#include <jni.h>
#include <string>
#include <android/log.h>
#include <assert.h>
#include <string.h>
#include "test-lib.h"
#include "_FileClass.h"

#define TAG "C++"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

extern "C"
JNIEXPORT jstring JNICALL native_stringFromJNI(JNIEnv *env, jclass type) {

    return env->NewStringUTF("C++");
}

JNIEXPORT jint JNICALL native_sum(JNIEnv *env, jclass type, jint a, jint b) {

    return sum(a, b);
}

static const JNINativeMethod gMethods[] = {
        {
                "stringFromJNI", "()Ljava/lang/String;", (void *) native_stringFromJNI
        },
        {
                "sum",           "(II)I",                (void *) native_sum
        }
};

static int registerNatives(JNIEnv *env) {
    LOGI("registerNatives begin");
    jclass clazz;
    //找到java的类
    clazz = env->FindClass("com/aruba/ndkapplication/JniUtils");

    if (clazz == NULL) {
        LOGI("clazz is null");
        return JNI_FALSE;
    }

    if (env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) {
        LOGI("RegisterNatives error");
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

//===================================文件拆分和合并方法start================================================
//二进制一个个字节写文件
void putPartFilec(JNIEnv *env, const long size, const char *partPath, CFILE *readFile) {
    CFILE partFile;
    //打开拆分文件
    if (!partFile.Open(partPath, "w")) {
        jclass newExc = env->FindClass("java/lang/Exception");
        char info[500];
        memset(info, 0, 500);
        sprintf(info, "can not open :%s", partPath);
        if (newExc != NULL) env->ThrowNew(newExc, info);
        env->DeleteLocalRef(newExc);

        return;
    }

    for (int j = 0; j < size; j++) {
        int c = readFile->Fgetc();
        partFile.Fputc(c);
    }
}

//拆分文件
JNIEXPORT void JNICALL
native_diff(JNIEnv *env, jclass type, jstring filePath, jstring partPath, jint num) {
    //读取的文件
    const char *readPath = env->GetStringUTFChars(filePath, NULL);
    //拆分文件
    const char *wirtePath = env->GetStringUTFChars(partPath, NULL);

    //读文件
    CFILE readFile;
    if (!readFile.Open(readPath, "r")) {
        jclass newExc = env->FindClass("java/lang/Exception");
        char info[500];
        memset(info, 0, 500);
        sprintf(info, "can not open :%s", filePath);
        if (newExc != NULL) env->ThrowNew(newExc, info);
        env->DeleteLocalRef(newExc);

        env->ReleaseStringUTFChars(partPath, wirtePath);
        env->ReleaseStringUTFChars(filePath, readPath);
        return;
    }

    //生成拆分文件绝对路径数组
    char partPaths[num][500];
    memset(partPaths, 0, sizeof(partPaths));
    for (int i = 0; i < num; i++) {
        sprintf(partPaths[i], wirtePath, i);
    }

    //计算每个文件大小
    long fileSize = readFile.GetFileSize();
    //整除的情况，均分
    if (fileSize % num == 0) {
        //每个拆分文件的大小
        long partFileSize = fileSize / num;

        //循环写入文件
        for (int i = 0; i < num; i++) {
            putPartFilec(env, partFileSize, partPaths[i], &readFile);
        }
    } else {
        //前（num - 1）个拆分文件的大小
        long partFileSize = fileSize / num;
        //循环写入文件
        for (int i = 0; i < num - 1; i++) {
            putPartFilec(env, partFileSize, partPaths[i], &readFile);
        }

        //第num个拆分文件的大小
        partFileSize = partFileSize + fileSize % num;
        putPartFilec(env, partFileSize, partPaths[num - 1], &readFile);
    }

    env->ReleaseStringUTFChars(partPath, wirtePath);
    env->ReleaseStringUTFChars(filePath, readPath);
}

//合并文件
JNIEXPORT void JNICALL
native_merge(JNIEnv *env, jclass type, jstring mergeFilePath, jstring partPath, jint num) {
    //写入的合并文件
    const char *wirtePath = env->GetStringUTFChars(mergeFilePath, NULL);
    //拆分文件
    const char *readPath = env->GetStringUTFChars(partPath, NULL);

    //生成拆分文件绝对路径数组
    char partPaths[num][500];
    memset(partPaths, 0, sizeof(partPaths));
    for (int i = 0; i < num; i++) {
        sprintf(partPaths[i], readPath, i);
    }

    //写合并文件
    CFILE mergeFile = new CFILE(false);
    if (!mergeFile.Open(wirtePath, "w")) {
        jclass newExc = env->FindClass("java/lang/Exception");
        char info[500];
        memset(info, 0, 500);
        sprintf(info, "can not open :%s", mergeFilePath);
        if (newExc != NULL) env->ThrowNew(newExc, info);
        env->DeleteLocalRef(newExc);

        env->ReleaseStringUTFChars(mergeFilePath, wirtePath);
        env->ReleaseStringUTFChars(partPath, readPath);
        return;
    }

    for (int i = 0; i < num; i++) {
        CFILE partFile;
        partFile.Open(partPaths[i], "r");
        long partFileSize = partFile.GetFileSize();
        for (int j = 0; j < partFileSize; j++) {
            mergeFile.Fputc(partFile.Fgetc());
        }
    }

    env->ReleaseStringUTFChars(mergeFilePath, wirtePath);
    env->ReleaseStringUTFChars(partPath, readPath);
}

//文件拆分和合并
static const JNINativeMethod gMethodsFileDiff[] = {
        {
                "diff",  "(Ljava/lang/String;Ljava/lang/String;I)V", (void *) native_diff
        },
        {
                "merge", "(Ljava/lang/String;Ljava/lang/String;I)V", (void *) native_merge
        }
};

//注册文件拆分和合并
static int registerNativesFileDiff(JNIEnv *env) {
    LOGI("registerNatives begin");
    jclass clazz;
    //找到java的类
    clazz = env->FindClass("com/aruba/ndkapplication/FileDiffUtils");

    if (clazz == NULL) {
        LOGI("clazz is null");
        return JNI_FALSE;
    }

    if (env->RegisterNatives(clazz, gMethodsFileDiff, NELEM(gMethodsFileDiff)) < 0) {
        LOGI("RegisterNatives error");
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

//===================================文件拆分和合并方法end================================================


//===================================多线程================================================

#include <pthread.h>

pthread_t pthid;
JavaVM *vm;
jobject g_obj;

//线程方法
void *thread_start(void *arg) {
    LOGI("thread_start begin");
    JNIEnv *env;
    //将线程添加到JVM中
    if (vm->AttachCurrentThread(&env, NULL) != JNI_OK) {
        LOGI("%s AttachCurrentThread error failed ", __FUNCTION__);
        return NULL;
    }

    sleep(3);
    //调用java中的getInfoFromC方法
    jclass clz = env->GetObjectClass(g_obj);
    jmethodID mid = env->GetStaticMethodID(clz, "getInfoFromC", "()V");
    env->CallStaticVoidMethod(clz, mid);
    
    pthread_exit(0);
}

//开启线程
JNIEXPORT void JNICALL
native_startThread(JNIEnv *env, jclass type) {
    LOGI("native_startThread begin");

    //创建线程
    pthread_create(&pthid, 0, thread_start, (void *) 1);
}

//设置环境
JNIEXPORT void JNICALL
native_set_env(JNIEnv *env, jobject jobj) {
    if (vm != NULL)
        vm = NULL;

    env->GetJavaVM(&vm);
    //保持ThreadUtils对象
    g_obj = env->NewGlobalRef(jobj);
}


//销毁资源
JNIEXPORT void JNICALL
native_destroy(JNIEnv *env, jclass type) {
    if (vm != NULL)
        vm = NULL;
    env->DeleteGlobalRef(g_obj);
}

//多线程
static const JNINativeMethod gMethodsThread[] = {
        {
                "startThread", "()V", (void *) native_startThread
        },
        {
                "setEnv",      "()V", (void *) native_set_env
        },
        {
                "destroy",     "()V", (void *) native_destroy
        }
};

//注册多线程Method
static int registerNativesThread(JNIEnv *env) {
    LOGI("registerNatives begin");
    jclass clazz;
    //找到java的类
    clazz = env->FindClass("com/aruba/ndkapplication/ThreadUtils");

    if (clazz == NULL) {
        LOGI("clazz is null");
        return JNI_FALSE;
    }

    if (env->RegisterNatives(clazz, gMethodsThread, NELEM(gMethodsThread)) < 0) {
        LOGI("RegisterNatives error");
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

//===================================多线程end================================================

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGI("jni_OnLoad begin");

    JNIEnv *env = NULL;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGI("ERROR: GetEnv failed\n");
        return -1;
    }

    assert(env != NULL);

    registerNatives(env);
    registerNativesFileDiff(env);
    registerNativesThread(env);

    return JNI_VERSION_1_4;
}